// Copyright Gary Evans 2006.

using System;
using System.Collections.Generic;
using System.Text;

using Tao.OpenGl;

using Taumuon.Jabuka.Physics;
using System.Drawing;

namespace Taumuon.Jabuka.MainApp
{
    /// <summary>
    /// Holds a list of IDrawable objects, and draws them using
    ///     OpenGL calls.
    /// Usage:
    ///   Construct with a list of IDrawable objects,
    ///    and rigid bodies which will be integrated.
    ///   Call InitGl before making any calls.
    ///   Call SetView before doing any drawing, and when the
    ///       size of the viewport changes.
    ///   Call RenderScene to draw the objects to the screen.
    ///   Call UpdateLocations to update the object locations.
    /// </summary>
    public class World
    {
        #region Member Variables

        List<IDrawable> drawableObjects = null;
        List<EulerRigidBody> rigidBodies = null;

        /// <summary>
        /// 
        /// </summary>
        public float ScaleFactor
        { get;  set; }

        /// <summary>
        /// 
        /// </summary>
        public int TranslateX
        { get;  set; }

        /// <summary>
        /// 
        /// </summary>
        public int TranslateY
        { get; set; }

        #endregion Member Variables

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="drawableObjects">
        /// List of objects that support OpenGl drawing.
        /// </param>
        /// <param name="rigidBodies">
        /// List of rigid bodies that update their physical parameters.
        /// </param>
        /// <exception cref="ArgumentNullException"/>
        public World(List<IDrawable> drawableObjects,
            List<EulerRigidBody> rigidBodies)
        {
            if (null == drawableObjects)
            {
                throw new ArgumentNullException("drawableObjects");
            }
            if (null == rigidBodies)
            {
                throw new ArgumentNullException("rigidBodies");
            }
            this.drawableObjects = drawableObjects;
            this.rigidBodies = rigidBodies;
            ScaleFactor = 1;
        }

        internal void Zoom(float scaleFactor, int height, int width)
        {
            ScaleFactor *= scaleFactor;
            SetView(height, width);
          
        }
        /// <summary>
        /// Draws a frame.
        /// </summary>
        internal void RenderScene()
        {
            // Clear Screen And Depth Buffer
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT |
                Gl.GL_DEPTH_BUFFER_BIT);
           
            Gl.glPushMatrix();
            Gl.glRotated(45, 0, 0, 1);
            // do our drawing here

            foreach (IDrawable drawableObject in drawableObjects)
            {
                drawableObject.Draw();
            }

            Gl.glPopMatrix();
            Gl.glFlush();
        }


        internal float nRange
        {
            get
            {
                return 3*drawableObjects[0].DrawSizeRectangle().Height* ScaleFactor;
            }
        }

 
        internal void SetView(int height, int width)
        {
            
            Gl.glViewport(0,0, width, height);

            // Reset projection matrix stack
            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();
        //  Gl.glRotated(
        //    Glu.gluLookAt(0, 0, 1, 0, 0, 0, 0, 1, 0);
        //    Gl.glFrustum(-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);
        //    Glu.gluLookAt(0, 0, 1.5, 0, 0, 0, 0, 1, 0);


            if (height == 0)
            {
                height = 1;
            }


            if (width <= height)
            {
                Gl.glOrtho(-nRange, nRange, -nRange * height / width,
                          nRange * height / width, -nRange, nRange);
            }
            else
            {
                Gl.glOrtho(-nRange * width / height,
                    nRange * width / height,
                    -nRange, nRange, -nRange, nRange);
            }

            // reset modelview matrix stack
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();
          

        


        }

        /// <summary>
        /// 
        /// </summary>
        public void UpdateCoordinate()
        {

            var pt = GetModelCoordinate();
            //foreach (EulerRigidBody rigidBody in rigidBodies)
            //{
            //    rigidBody.Update(pt.X, pt.Y, 0);
            //}
        }

        private  Point GetModelCoordinate()
        {
            var modelView = new double[16];
            Gl.glGetDoublev(Gl.GL_MODELVIEW_MATRIX, modelView);

            var projection = new double[16];
            Gl.glGetDoublev(Gl.GL_PROJECTION_MATRIX, projection);

            var viewPort = new int[4];
            Gl.glGetIntegerv(Gl.GL_VIEWPORT, viewPort);

            double orgX = 0;
            double orgY = 0;
            double orgZ = 0;

            int[] zPtr = new int[] { 0};
            Gl.glReadPixels(TranslateX, viewPort[3] - TranslateY, 1, 1, Gl.GL_DEPTH_COMPONENT,
                Gl.GL_FLOAT, zPtr);

            Glu.gluUnProject(TranslateX, viewPort[3] - TranslateY, zPtr[0], modelView, projection,
                viewPort, out orgX, out orgY, out orgZ);
            return new Point((int)orgX, (int)orgY);
        }

        internal void InitGl()
        {
            Gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            Gl.glShadeModel(Gl.GL_FLAT);
            Gl.glEnable(Gl.GL_DEPTH_TEST);
            Gl.glEnable(Gl.GL_CULL_FACE);
        }

        internal void UpdateLocations()
        {
            const double increment = 0.1;
            foreach (EulerRigidBody rigidBody in rigidBodies)
            {
                rigidBody.Update(increment);
            }
        }

        internal void ResetScene()
        {
            foreach (EulerRigidBody rigidBody in rigidBodies)
            {
                rigidBody.Location.X = 0.0;
                rigidBody.Location.Y = 0.0;
                rigidBody.Location.Z = 0.0;
                rigidBody.Velocity.X = 0.0;
                rigidBody.Velocity.Y = 0.0;
                rigidBody.Velocity.Z = 0.0;
            }
            // TODO: hack! World shouldn't have knowledge
            //  of objects initialisation!
            rigidBodies[0].Location.X = 20.0;
            rigidBodies[0].Velocity.X = 2.0;
            rigidBodies[0].Velocity.Y = 10.0;

            rigidBodies[1].Velocity.Y = 1.0;

        }
    }
}
